// Copyright (C) 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Install a leaky WeakMap emulation on platforms that
 * don't provide a built-in one.
 *
 * <p>Assumes that an ES5 platform where, if {@code WeakMap} is
 * already present, then it conforms to the anticipated ES6
 * specification. To run this file on an ES5 or almost ES5
 * implementation where the {@code WeakMap} specification does not
 * quite conform, run <code>repairES5.js</code> first.
 *
 * <p>Even though WeakMapModule is not global, the linter thinks it
 * is, which is why it is in the overrides list below.
 *
 * <p>NOTE: Before using this WeakMap emulation in a non-SES
 * environment, see the note below about hiddenRecord.
 *
 * @author Mark S. Miller
 * @requires crypto, ArrayBuffer, Uint8Array, navigator, console
 * @overrides WeakMap, ses, Proxy
 * @overrides WeakMapModule
 */

/**
 * This {@code WeakMap} emulation is observably equivalent to the
 * ES-Harmony WeakMap, but with leakier garbage collection properties.
 *
 * <p>As with true WeakMaps, in this emulation, a key does not
 * retain maps indexed by that key and (crucially) a map does not
 * retain the keys it indexes. A map by itself also does not retain
 * the values associated with that map.
 *
 * <p>However, the values associated with a key in some map are
 * retained so long as that key is retained and those associations are
 * not overridden. For example, when used to support membranes, all
 * values exported from a given membrane will live for the lifetime
 * they would have had in the absence of an interposed membrane. Even
 * when the membrane is revoked, all objects that would have been
 * reachable in the absence of revocation will still be reachable, as
 * far as the GC can tell, even though they will no longer be relevant
 * to ongoing computation.
 *
 * <p>The API implemented here is approximately the API as implemented
 * in FF6.0a1 and agreed to by MarkM, Andreas Gal, and Dave Herman,
 * rather than the offially approved proposal page. TODO(erights):
 * upgrade the ecmascript WeakMap proposal page to explain this API
 * change and present to EcmaScript committee for their approval.
 *
 * <p>The first difference between the emulation here and that in
 * FF6.0a1 is the presence of non enumerable {@code get___, has___,
 * set___, and delete___} methods on WeakMap instances to represent
 * what would be the hidden internal properties of a primitive
 * implementation. Whereas the FF6.0a1 WeakMap.prototype methods
 * require their {@code this} to be a genuine WeakMap instance (i.e.,
 * an object of {@code [[Class]]} "WeakMap}), since there is nothing
 * unforgeable about the pseudo-internal method names used here,
 * nothing prevents these emulated prototype methods from being
 * applied to non-WeakMaps with pseudo-internal methods of the same
 * names.
 *
 * <p>Another difference is that our emulated {@code
 * WeakMap.prototype} is not itself a WeakMap. A problem with the
 * current FF6.0a1 API is that WeakMap.prototype is itself a WeakMap
 * providing ambient mutability and an ambient communications
 * channel. Thus, if a WeakMap is already present and has this
 * problem, repairES5.js wraps it in a safe wrappper in order to
 * prevent access to this channel. (See
 * PATCH_MUTABLE_FROZEN_WEAKMAP_PROTO in repairES5.js).
 */
var WeakMap;

/**
 * If this is a full <a href=
 * "http://code.google.com/p/es-lab/wiki/SecureableES5"
 * >secureable ES5</a> platform and the ES-Harmony {@code WeakMap} is
 * absent, install an approximate emulation.
 *
 * <p>If WeakMap is present but cannot store some objects, use our approximate
 * emulation as a wrapper.
 *
 * <p>If this is almost a secureable ES5 platform, then WeakMap.js
 * should be run after repairES5.js.
 *
 * <p>See {@code WeakMap} for documentation of the garbage collection
 * properties of this WeakMap emulation.
 */
(function WeakMapModule() {
  "use strict";

  if (typeof ses !== 'undefined' && ses.okToLoad && !ses.okToLoad()) {
    // already too broken, so give up
    return;
  }

  function constFunc(func) {
    func.prototype = null;
    return Object.freeze(func);
  }

  var calledAsFunctionWarningDone = false;
  function calledAsFunctionWarning() {
    // Future ES6 WeakMap is currently (2013-09-10) expected to reject WeakMap()
    // but we used to permit it and do it ourselves, so warn only.
    if (!calledAsFunctionWarningDone && typeof console !== 'undefined') {
      calledAsFunctionWarningDone = true;
      console.warn('WeakMap should be invoked as new WeakMap(), not ' +
          'WeakMap(). This will be an error in the future.');
    }
  }

  // used by implementWeakMap()
  var hiddenNameInstalled = false;

  /**
   * In some cases (current Firefox), we must make a choice betweeen a
   * WeakMap which is capable of using all varieties of host objects as
   * keys and one which is capable of safely using proxies as keys. See
   * comments below about HostWeakMap and DoubleWeakMap for details.
   *
   * This function (which is a global, not exposed to guests) marks a
   * WeakMap as permitted to do what is necessary to index all host
   * objects, at the cost of making it unsafe for proxies.
   *
   * Do not apply this function to anything which is not a genuine
   * fresh WeakMap.
   */
  function weakMapPermitHostObjects(map) {
    // identity of function used as a secret -- good enough and cheap
    if (map.permitHostObjects___) {
      map.permitHostObjects___(weakMapPermitHostObjects);
    }
  }
  if (typeof ses !== 'undefined') {
    ses.weakMapPermitHostObjects = weakMapPermitHostObjects;
  }

  var repairer = ses._repairer || new ses._Repairer();
  var severities = ses.severities;

  repairer.registerProblem({
    id: 'WEAKMAP_DOES_NOT_EXIST',
    description: 'WeakMap does not exist',
    test: function() {
      return typeof WeakMap !== 'function';
    },
    repair: function() {
      WeakMap = implementWeakMap();

      // Emulated WeakMaps are incompatible with native proxies (because proxies
      // can observe the hidden name), so we must disable Proxy usage (in
      // ArrayLike and Domado, currently).
      if (typeof Proxy !== 'undefined') {
        Proxy = undefined;
      }
    },
    preSeverity: severities.SAFE_SPEC_VIOLATION,
    canRepair: true,
    urls: [],
    sections: [],  // TODO(kpreid): cite ES6 when published
    tests: []  // TODO(kpreid): should be in test262
  });
  repairer.registerProblem({
    id: 'WEAKMAP_REJECTS_SOME_KEYS',
    description: 'WeakMap throws when given some keys',
    test: function() {
      if (typeof WeakMap === 'undefined') { return false; }

      // detect our repair
      if (WeakMap.name === 'DoubleWeakMap') { return false; }

      // As of this writing (2013-05-06) Firefox's WeakMaps have a miscellany
      // of objects they won't accept, and we don't want to make an exhaustive
      // list, and testing for just one will be a problem if that one is fixed
      // alone (as they did for Event).
      return (typeof navigator !== 'undefined' &&
          /Firefox/.test(navigator.userAgent));

      // If there is a platform that we *can* reliably test on, here's how to
      // do it:
      //  var problematic = ... ;
      //  var testHostMap = new HostWeakMap();
      //  try {
      //    testHostMap.set(problematic, 1);  // Firefox 20 will throw here
      //    if (testHostMap.get(problematic) === 1) {
      //      return false;
      //    } else {
      //      return 'Neither threw nor stored';
      //    }
      //  } catch (e) { return true; }
    },
    repair: function() {
      WeakMap = wrapWeakMap(WeakMap, false);
    },
    preSeverity: severities.SAFE_SPEC_VIOLATION,
    canRepair: true,
    urls: ['https://bugzilla.mozilla.org/show_bug.cgi?id=803844'],
    sections: [],  // TODO(kpreid): cite ES6 when published
    tests: []  // TODO(kpreid): should be in test262
  });
  repairer.registerProblem({
    id: 'WEAKMAP_DROPS_FROZEN_KEYS',
    description: 'WeakMap drops frozen keys',
    test: function() {
      // IE 11 bug: WeakMaps silently fail to store frozen objects.
      if (typeof WeakMap === 'undefined') { return false; }
      var testMap = new WeakMap();
      var testObject = Object.freeze({});
      testMap.set(testObject, 1);
      var result = testMap.get(testObject);
      if (result === 1) {
        return false;
      } else if (result === undefined) {
        return true;
      } else {
        return 'Unexpected get() result: ' + result;
      }
    },
    repair: function() {
      WeakMap = wrapWeakMap(WeakMap, true);

      // In this mode we are always using double maps, so we are not proxy-safe.
      // The combination of WEAKMAP_DROPS_FROZEN_KEYS and supporting Proxy
      // does not occur in any known browser, but we had best be safe.
      if (typeof Proxy !== 'undefined') {
        Proxy = undefined;
      }
    },
    preSeverity: severities.UNSAFE_SPEC_VIOLATION,
    canRepair: true,
    urls: ['https://connect.microsoft.com/IE/feedback/details/871401/weakmaps-drop-frozen-keys'],
    sections: [],  // TODO(kpreid): cite ES6 when published
    tests: []  // TODO(kpreid): should be in test262
  });

  repairer.testAndRepair();

  // Implementation -- code after here is only functions

  function implementWeakMap() {
    // paranoia since wrapWeakMap calls implementWeakMap as well as itself
    // being a repair
    if (hiddenNameInstalled) {
      throw new Error("shouldn't happen: implementWeakMap called twice");
    }
    hiddenNameInstalled = true;

    var hop = Object.prototype.hasOwnProperty;
    var gopn = Object.getOwnPropertyNames;
    var defProp = Object.defineProperty;
    var isExtensible = Object.isExtensible;

    /**
     * Security depends on HIDDEN_NAME being both <i>unguessable</i> and
     * <i>undiscoverable</i> by untrusted code.
     *
     * <p>Given the known weaknesses of Math.random() on existing
     * browsers, it does not generate unguessability we can be confident
     * of.
     *
     * <p>It is the monkey patching logic in this file that is intended
     * to ensure undiscoverability. The basic idea is that there are
     * three fundamental means of discovering properties of an object:
     * The for/in loop, Object.keys(), and Object.getOwnPropertyNames(),
     * as well as some proposed ES6 extensions that appear on our
     * whitelist. The first two only discover enumerable properties, and
     * we only use HIDDEN_NAME to name a non-enumerable property, so the
     * only remaining threat should be getOwnPropertyNames and some
     * proposed ES6 extensions that appear on our whitelist. We monkey
     * patch them to remove HIDDEN_NAME from the list of properties they
     * returns.
     *
     * <p>TODO(erights): On a platform with built-in Proxies, proxies
     * could be used to trap and thereby discover the HIDDEN_NAME, so we
     * need to monkey patch Proxy.create, Proxy.createFunction, etc, in
     * order to wrap the provided handler with the real handler which
     * filters out all traps using HIDDEN_NAME.
     *
     * <p>TODO(erights): Revisit Mike Stay's suggestion that we use an
     * encapsulated function at a not-necessarily-secret name, which
     * uses the Stiegler shared-state rights amplification pattern to
     * reveal the associated value only to the WeakMap in which this key
     * is associated with that value. Since only the key retains the
     * function, the function can also remember the key without causing
     * leakage of the key, so this doesn't violate our general gc
     * goals. In addition, because the name need not be a guarded
     * secret, we could efficiently handle cross-frame frozen keys.
     */
    var HIDDEN_NAME_PREFIX = 'weakmap:';
    var HIDDEN_NAME = HIDDEN_NAME_PREFIX + 'ident:' + Math.random() + '___';

    if (typeof crypto !== 'undefined' &&
        typeof crypto.getRandomValues === 'function' &&
        typeof ArrayBuffer === 'function' &&
        typeof Uint8Array === 'function') {
      var ab = new ArrayBuffer(25);
      var u8s = new Uint8Array(ab);
      crypto.getRandomValues(u8s);
      HIDDEN_NAME = HIDDEN_NAME_PREFIX + 'rand:' +
        Array.prototype.map.call(u8s, function(u8) {
          return (u8 % 36).toString(36);
        }).join('') + '___';
    }

    function isNotHiddenName(name) {
      return !(
          name.substr(0, HIDDEN_NAME_PREFIX.length) == HIDDEN_NAME_PREFIX &&
          name.substr(name.length - 3) === '___');
    }

    // For all calls to Object.defineProperty (defProp) to redefine an
    // existing property, keep in mind that omitting some attributes,
    // like writable:, enumerable:, or configurable:, means that the
    // current setting of these attributes should be preseved, rather
    // than defaulting to false.

    // Note that the use of .filter as an array instance method below
    // only works in SES under the immutable primordials
    // assumption. For example, it would not work in CES (Confined
    // EcmaScript).

    /**
     * Monkey patch getOwnPropertyNames to avoid revealing the
     * HIDDEN_NAME.
     *
     * <p>The ES5.1 spec requires each name to appear only once, but as
     * of this writing, this requirement is controversial for ES6, so we
     * made this code robust against this case. If the resulting extra
     * search turns out to be expensive, we can probably relax this once
     * ES6 is adequately supported on all major browsers, iff no browser
     * versions we support at that time have relaxed this constraint
     * without providing built-in ES6 WeakMaps.
     */
    defProp(Object, 'getOwnPropertyNames', {
      value: function fakeGetOwnPropertyNames(obj) {
        return gopn(obj).filter(isNotHiddenName);
      }
    });

    /**
     * getPropertyNames is not in ES5 but it is proposed for ES6 and
     * does appear in our whitelist, so we need to clean it too.
     */
    if ('getPropertyNames' in Object) {
      var originalGetPropertyNames = Object.getPropertyNames;
      defProp(Object, 'getPropertyNames', {
        value: function fakeGetPropertyNames(obj) {
          return originalGetPropertyNames(obj).filter(isNotHiddenName);
        }
      });
    }

    /**
     * <p>To treat objects as identity-keys with reasonable efficiency
     * on ES5 by itself (i.e., without any object-keyed collections), we
     * need to add a hidden property to such key objects when we
     * can. This raises several issues:
     * <ul>
     * <li>Arranging to add this property to objects before we lose the
     *     chance, and
     * <li>Hiding the existence of this new property from most
     *     JavaScript code.
     * <li>Preventing <i>certification theft</i>, where one object is
     *     created falsely claiming to be the key of an association
     *     actually keyed by another object.
     * <li>Preventing <i>value theft</i>, where untrusted code with
     *     access to a key object but not a weak map nevertheless
     *     obtains access to the value associated with that key in that
     *     weak map.
     * </ul>
     * We do so by
     * <ul>
     * <li>Making the name of the hidden property unguessable, so "[]"
     *     indexing, which we cannot intercept, cannot be used to access
     *     a property without knowing the name.
     * <li>Making the hidden property non-enumerable, so we need not
     *     worry about for-in loops or {@code Object.keys},
     * <li>monkey patching those reflective methods that would
     *     prevent extensions, to add this hidden property first,
     * <li>monkey patching those methods that would reveal this
     *     hidden property.
     * </ul>
     * Unfortunately, because of same-origin iframes, we cannot reliably
     * add this hidden property before an object becomes
     * non-extensible. Instead, if we encounter a non-extensible object
     * without a hidden record that we can detect (whether or not it has
     * a hidden record stored under a name secret to us), then we just
     * use the key object itself to represent its identity in a brute
     * force leaky map stored in the weak map, losing all the advantages
     * of weakness for these.
     */
    function getHiddenRecord(key) {
      if (key !== Object(key)) {
        throw new TypeError('Not an object: ' + key);
      }
      var hiddenRecord = key[HIDDEN_NAME];
      if (hiddenRecord && hiddenRecord.key === key) { return hiddenRecord; }
      if (!isExtensible(key)) {
        // Weak map must brute force, as explained in doc-comment above.
        return void 0;
      }

      // The hiddenRecord and the key point directly at each other, via
      // the "key" and HIDDEN_NAME properties respectively. The key
      // field is for quickly verifying that this hidden record is an
      // own property, not a hidden record from up the prototype chain.
      //
      // NOTE: Because this WeakMap emulation is meant only for systems like
      // SES where Object.prototype is frozen without any numeric
      // properties, it is ok to use an object literal for the hiddenRecord.
      // This has two advantages:
      // * It is much faster in a performance critical place
      // * It avoids relying on Object.create(null), which had been
      //   problematic on Chrome 28.0.1480.0. See
      //   https://code.google.com/p/google-caja/issues/detail?id=1687
      hiddenRecord = { key: key };

      // When using this WeakMap emulation on platforms where
      // Object.prototype might not be frozen and Object.create(null) is
      // reliable, use the following two commented out lines instead.
      // hiddenRecord = Object.create(null);
      // hiddenRecord.key = key;

      // Please contact us if you need this to work on platforms where
      // Object.prototype might not be frozen and
      // Object.create(null) might not be reliable.

      defProp(key, HIDDEN_NAME, {
        value: hiddenRecord,
        writable: false,
        enumerable: false,
        configurable: false
      });
      return hiddenRecord;
    }


    /**
     * Monkey patch operations that would make their argument
     * non-extensible.
     *
     * <p>The monkey patched versions throw a TypeError if their
     * argument is not an object, so it should only be done to functions
     * that should throw a TypeError anyway if their argument is not an
     * object.
     */
    (function(){
      var oldFreeze = Object.freeze;
      defProp(Object, 'freeze', {
        value: function identifyingFreeze(obj) {
          getHiddenRecord(obj);
          return oldFreeze(obj);
        }
      });
      var oldSeal = Object.seal;
      defProp(Object, 'seal', {
        value: function identifyingSeal(obj) {
          getHiddenRecord(obj);
          return oldSeal(obj);
        }
      });
      var oldPreventExtensions = Object.preventExtensions;
      defProp(Object, 'preventExtensions', {
        value: function identifyingPreventExtensions(obj) {
          getHiddenRecord(obj);
          return oldPreventExtensions(obj);
        }
      });
    })();


    function constFunc(func) {
      func.prototype = null;
      return Object.freeze(func);
    }

    var calledAsFunctionWarningDone = false;
    function calledAsFunctionWarning() {
      // Future ES6 WeakMap is currently (2013-09-10) expected to reject WeakMap()
      // but we used to permit it and do it ourselves, so warn only.
      if (!calledAsFunctionWarningDone && typeof console !== 'undefined') {
        calledAsFunctionWarningDone = true;
        console.warn('WeakMap should be invoked as new WeakMap(), not ' +
            'WeakMap(). This will be an error in the future.');
      }
    }

    var nextId = 0;

    var OurWeakMap = function() {
      if (!(this instanceof OurWeakMap)) {  // approximate test for new ...()
        calledAsFunctionWarning();
      }

      // We are currently (12/25/2012) never encountering any prematurely
      // non-extensible keys.
      var keys = []; // brute force for prematurely non-extensible keys.
      var values = []; // brute force for corresponding values.
      var id = nextId++;

      function get___(key, opt_default) {
        var index;
        var hiddenRecord = getHiddenRecord(key);
        if (hiddenRecord) {
          return id in hiddenRecord ? hiddenRecord[id] : opt_default;
        } else {
          index = keys.indexOf(key);
          return index >= 0 ? values[index] : opt_default;
        }
      }

      function has___(key) {
        var hiddenRecord = getHiddenRecord(key);
        if (hiddenRecord) {
          return id in hiddenRecord;
        } else {
          return keys.indexOf(key) >= 0;
        }
      }

      function set___(key, value) {
        var index;
        var hiddenRecord = getHiddenRecord(key);
        if (hiddenRecord) {
          hiddenRecord[id] = value;
        } else {
          index = keys.indexOf(key);
          if (index >= 0) {
            values[index] = value;
          } else {
            // Since some browsers preemptively terminate slow turns but
            // then continue computing with presumably corrupted heap
            // state, we here defensively get keys.length first and then
            // use it to update both the values and keys arrays, keeping
            // them in sync.
            index = keys.length;
            values[index] = value;
            // If we crash here, values will be one longer than keys.
            keys[index] = key;
          }
        }
        return this;
      }

      function delete___(key) {
        var hiddenRecord = getHiddenRecord(key);
        var index, lastIndex;
        if (hiddenRecord) {
          return id in hiddenRecord && delete hiddenRecord[id];
        } else {
          index = keys.indexOf(key);
          if (index < 0) {
            return false;
          }
          // Since some browsers preemptively terminate slow turns but
          // then continue computing with potentially corrupted heap
          // state, we here defensively get keys.length first and then use
          // it to update both the keys and the values array, keeping
          // them in sync. We update the two with an order of assignments,
          // such that any prefix of these assignments will preserve the
          // key/value correspondence, either before or after the delete.
          // Note that this needs to work correctly when index === lastIndex.
          lastIndex = keys.length - 1;
          keys[index] = void 0;
          // If we crash here, there's a void 0 in the keys array, but
          // no operation will cause a "keys.indexOf(void 0)", since
          // getHiddenRecord(void 0) will always throw an error first.
          values[index] = values[lastIndex];
          // If we crash here, values[index] cannot be found here,
          // because keys[index] is void 0.
          keys[index] = keys[lastIndex];
          // If index === lastIndex and we crash here, then keys[index]
          // is still void 0, since the aliasing killed the previous key.
          keys.length = lastIndex;
          // If we crash here, keys will be one shorter than values.
          values.length = lastIndex;
          return true;
        }
      }

      return Object.create(OurWeakMap.prototype, {
        get___:    { value: constFunc(get___) },
        has___:    { value: constFunc(has___) },
        set___:    { value: constFunc(set___) },
        delete___: { value: constFunc(delete___) }
      });
    };

    OurWeakMap.prototype = Object.create(Object.prototype, {
      get: {
        /**
         * Return the value most recently associated with key, or
         * opt_default if none.
         */
        value: function get(key, opt_default) {
          return this.get___(key, opt_default);
        },
        writable: true,
        configurable: true
      },

      has: {
        /**
         * Is there a value associated with key in this WeakMap?
         */
        value: function has(key) {
          return this.has___(key);
        },
        writable: true,
        configurable: true
      },

      set: {
        /**
         * Associate value with key in this WeakMap, overwriting any
         * previous association if present.
         */
        value: function set(key, value) {
          return this.set___(key, value);
        },
        writable: true,
        configurable: true
      },

      'delete': {
        /**
         * Remove any association for key in this WeakMap, returning
         * whether there was one.
         *
         * <p>Note that the boolean return here does not work like the
         * {@code delete} operator. The {@code delete} operator returns
         * whether the deletion succeeds at bringing about a state in
         * which the deleted property is absent. The {@code delete}
         * operator therefore returns true if the property was already
         * absent, whereas this {@code delete} method returns false if
         * the association was already absent.
         */
        value: function remove(key) {
          return this.delete___(key);
        },
        writable: true,
        configurable: true
      }
    });

    return OurWeakMap;
  }

  /**
   * @param doubleWeakMapCheckSilentFailure IE 11 has no Proxy but has a broken
   * WeakMap such that we need to patch it using DoubleWeakMap; this flag tells
   * DoubleWeakMap so.
   */
  function wrapWeakMap(HostWeakMap, doubleWeakMapCheckSilentFailure) {
    // The platform has a WeakMap but we are concerned that it may refuse to
    // store some key types. Therefore, make a map implementation which makes
    // use of both as possible.

    var OurWeakMap = implementWeakMap();

    function DoubleWeakMap() {
      if (!(this instanceof OurWeakMap)) {  // approximate test for new ...()
        calledAsFunctionWarning();
      }

      // Preferable, truly weak map.
      var hmap = new HostWeakMap();

      // Our hidden-property-based pseudo-weak-map. Lazily initialized in the
      // 'set' implementation; thus we can avoid performing extra lookups if
      // we know all entries actually stored are entered in 'hmap'.
      var omap = undefined;

      // Hidden-property maps are not compatible with proxies because proxies
      // can observe the hidden name and either accidentally expose it or fail
      // to allow the hidden property to be set. Therefore, we do not allow
      // arbitrary WeakMaps to switch to using hidden properties, but only
      // those which need the ability, and unprivileged code is not allowed
      // to set the flag.
      //
      // (Except in doubleWeakMapCheckSilentFailure mode in which case we
      // disable proxies.)
      var enableSwitching = false;

      function dget(key, opt_default) {
        if (omap) {
          return hmap.has(key) ? hmap.get(key)
              : omap.get___(key, opt_default);
        } else {
          return hmap.get(key, opt_default);
        }
      }

      function dhas(key) {
        return hmap.has(key) || (omap ? omap.has___(key) : false);
      }

      var dset;
      if (doubleWeakMapCheckSilentFailure) {
        dset = function(key, value) {
          hmap.set(key, value);
          if (!hmap.has(key)) {
            if (!omap) { omap = new OurWeakMap(); }
            omap.set(key, value);
          }
          return this;
        };
      } else {
        dset = function(key, value) {
          if (enableSwitching) {
            try {
              hmap.set(key, value);
            } catch (e) {
              if (!omap) { omap = new OurWeakMap(); }
              omap.set___(key, value);
            }
          } else {
            hmap.set(key, value);
          }
          return this;
        };
      }

      function ddelete(key) {
        var result = !!hmap['delete'](key);
        if (omap) { return omap.delete___(key) || result; }
        return result;
      }

      return Object.create(OurWeakMap.prototype, {
        get___:    { value: constFunc(dget) },
        has___:    { value: constFunc(dhas) },
        set___:    { value: constFunc(dset) },
        delete___: { value: constFunc(ddelete) },
        permitHostObjects___: { value: constFunc(function(token) {
          if (token === weakMapPermitHostObjects) {
            enableSwitching = true;
          } else {
            throw new Error('bogus call to permitHostObjects___');
          }
        })}
      });
    }
    DoubleWeakMap.prototype = OurWeakMap.prototype;

    // define .constructor to hide OurWeakMap ctor
    Object.defineProperty(DoubleWeakMap.prototype, 'constructor', {
      value: DoubleWeakMap,
      enumerable: false,  // as default .constructor is
      configurable: true,
      writable: true
    });

    return DoubleWeakMap;
  }

})();
